/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

import { cloneDeep } from '@baize/wdk';

import { BuildModeConst, BuildOptionConst } from '../../const/build-mode-const.js';
import { DefaultTargetConst } from '../../const/common-const.js';
import { ModuleModel } from '../../model/module/module-model.js';
import { ProjectModel } from '../../model/project/project-model.js';
import { BuildOpt, BuildOptionWithName } from '../../options/build/build-opt.js';
import { ModuleBuildProfile } from '../../options/build/module-build-profile.js';
import { Option } from '../../options/options.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import BuildModeBinder = ModuleBuildProfile.BuildModeBinder;
import BuildModeBinderMapping = ModuleBuildProfile.BuildModeBinderMapping;

import { Attributes } from '../../common/build-option-path-info.js';

const log = OhosLogger.getLogger('BuildOptionUtil');

export class BuildOptionUtil {
  /**
   * 根据buildOptionName获取隐式的默认配置
   *
   * @param {string} buildOptName buildOption的名字
   * @returns {BuildOpt}
   */
  public static getDefaultModuleBuildOpt(buildOptName: string): BuildOpt {
    /*
     * default：保底选项，参数 debuggable=true
     * debug：从 default 处拷贝
     * release：从 default 处拷贝，覆写参数 debuggable=false
     */
    const defaultBuildOptionSet: Record<string, BuildOpt> = {
      debug: {
        debuggable: true,
        copyFrom: 'default',
      },
      release: {
        debuggable: false,
        copyFrom: 'default',
      },
      default: { debuggable: true },
    };

    return defaultBuildOptionSet[buildOptName] ?? {};
  }

  /**
   * 根据buildModeName获取隐式的默认配置
   *
   * @param {string} buildMode buildMode的名字
   * @returns {BuildOpt}
   */
  public static getDefaultBuildModeBuildOpt(buildMode: string): BuildOpt {
    /*
     * 对于内置的三种 build mode:
     * debug：debuggable=true
     * release：debuggable=false
     * test：debuggable=true
     */
    const defaultBuildModeOptions: Record<string, BuildOpt> = {
      debug: {
        debuggable: true,
      },
      release: {
        debuggable: false,
      },
      test: {
        debuggable: true,
      },
    };

    return defaultBuildModeOptions[buildMode] ?? {};
  }

  /**
   * 在BuildModeBinderSet里面配置默认的绑定关系
   * 对于系统内置的三种 build mode（debug / release / test）, Hvigor 会分配默认绑定：
   *
   * - debug mode：优先分配 debug buildOption，测试包（ohosTest）分配 default buildOption
   * - release mode：优先分配 release buildOption，测试包（ohosTest）分配 default buildOption
   * - test mode：优先分配 debug buildOption，测试包（ohosTest）分配 default buildOption，
   *
   * @param {ModuleBuildProfile.BuildModeBinder[]} binderSet
   * @returns {ModuleBuildProfile.BuildModeBinder[]}
   */
  public static getOrDefaultBuildModeBinderSet(binderSet: BuildModeBinder[]): BuildModeBinder[] {
    const combinedBinderSet = this.arrayDeduplication(binderSet, 'buildModeName');
    combinedBinderSet.forEach((binder) => {
      if (binder.mappings) {
        binder.mappings = this.arrayDeduplication(binder.mappings, 'targetName');
      }
    });
    const defaultBuildModes = [BuildModeConst.DEBUG, BuildModeConst.RELEASE, BuildModeConst.TEST];
    defaultBuildModes.forEach((buildModeName) => {
      if (!binderSet.some((binder) => binder.buildModeName === buildModeName)) {
        combinedBinderSet.push({
          buildModeName: buildModeName,
          mappings: [
            {
              targetName: DefaultTargetConst.DEFAULT_TARGET,
              buildOptionName: buildModeName === BuildModeConst.RELEASE ? BuildOptionConst.RELEASE : BuildOptionConst.DEBUG,
            },
            {
              targetName: DefaultTargetConst.OHOS_TEST_TARGET,
              buildOptionName: BuildOptionConst.DEFAULT,
            },
          ],
        });
      }
    });
    return combinedBinderSet;
  }

  static arrayDeduplication<T extends Record<string, unknown>>(array: T[], uniqueKey = 'name'): T[] {
    const nameSet = new Set();
    const newArray: T[] = [];

    // 去重操作
    for (let i = array.length - 1; i >= 0; i--) {
      const element = array[i];
      if (!nameSet.has(element[uniqueKey])) {
        newArray.push(element);
        nameSet.add(element[uniqueKey]);
      }
    }
    return newArray;
  }

  // 校验buildMode是否在工程级build-profile.josn5中配置，如果未配置则返回相应的数据并在全局记录
  public static validateBuildMode(buildModeName: string, projectModel: ProjectModel) {
    // buildMode必须为工程级配置buildModeSet内元素（包含隐式的默认配置）
    const buildModeNames: string[] = (projectModel.getProfileOpt().app.buildModeSet ?? [])
      .map((buildMode) => buildMode.name)
      .concat([BuildModeConst.DEBUG, BuildModeConst.RELEASE, BuildModeConst.TEST]);
    return !buildModeNames.some((name) => name === buildModeName)
  }

  /**
   * 检查binderSet里面buildMode、targetName和buildOption的合法性
   * - buildMode必须为工程级配置buildModeSet内元素（包含隐式的默认配置）
   * - mapping内targetName必须为该模块内的target的name
   * - mapping内buildOptionName必须为该模块内的buildOptionSet内的对应name（包括module顶层的buildOption）
   *
   * @param {ModuleModel} moduleModel
   */
  public static validateBinderSet(moduleModel: ModuleModel) {
    const buildBinderSet: BuildModeBinder[] | undefined = moduleModel.getProfileOpt().buildModeBinder;
    if (buildBinderSet === undefined) {
      return;
    }
    const binderSetBuildModeNames = buildBinderSet.map((binder) => binder.buildModeName);
    const binderMappings: BuildModeBinderMapping[] = buildBinderSet.map((binder) => binder.mappings ?? []).flat();
    const binderTargetNames = binderMappings.map((mapping) => mapping.targetName);
    const binderBuildOptNames = binderMappings.map((mapping) => mapping.buildOptionName);

    // buildMode必须为工程级配置buildModeSet内元素（包含隐式的默认配置）
    const buildModeNames: string[] = (moduleModel.getParentProject().getProfileOpt().app.buildModeSet ?? [])
      .map((buildMode) => buildMode.name)
      .concat([BuildModeConst.DEBUG, BuildModeConst.RELEASE, BuildModeConst.TEST]);
    this.validateBinderSetBuildModes(buildModeNames, binderSetBuildModeNames, moduleModel);

    // mapping内targetName必须为该模块内的target的name
    const targetNames: string[] = moduleModel
      .getTargetOptions()
      .map((target) => target.name)
      .concat([DefaultTargetConst.DEFAULT_TARGET]);
    this.validateBinderSetTargetNames(targetNames, binderTargetNames, moduleModel);

    // mapping内buildOptionName必须为该模块内的buildOptionSet内的对应name
    const buildOptNames = (moduleModel.getProfileOpt().buildOptionSet ?? [])
      .map((buildOpt) => buildOpt.name)
      .concat([BuildOptionConst.DEFAULT, BuildOptionConst.DEBUG, BuildOptionConst.RELEASE]);
    this.validateBinderSetBuildOptNames(buildOptNames, binderBuildOptNames, moduleModel);
  }

  /**
   * buildOptionSet 校验
   * -- copyFrom 校验
   * ---- 校验 copyFrom名字是否存在，提示warning
   * ---- 校验 copyFrom 是否成环
   *
   * @param {ModuleModel} moduleModel
   */
  public static validateBuildOptionSet(moduleModel: ModuleModel) {
    const buildOptionSet = moduleModel.getProfileOpt().buildOptionSet;
    if (buildOptionSet === undefined) {
      return;
    }
    const buildOptionsNames = buildOptionSet.map((buildOption) => buildOption.name);
    [BuildOptionConst.DEFAULT, BuildOptionConst.DEBUG, BuildOptionConst.RELEASE].forEach((buildInName) => {
      if (!buildOptionsNames.includes(buildInName)) {
        buildOptionSet.push({
          name: buildInName,
        });
      }
    });
    const buildOptionMap = this.buildOptionSetToMap(buildOptionSet);

    // 校验 copyFrom名字是否存在
    this.validateCopyFromNameExists(moduleModel, buildOptionSet, buildOptionMap);

    // 校验 copyFrom 是否成环
    this.validateCopyFromCircle(moduleModel, buildOptionSet, buildOptionMap);
  }

  public static isCopyFromNameExists(buildOption: BuildOptionWithName, buildOptionMap: Map<string, BuildOptionWithName>) {
    return buildOption.copyFrom && buildOptionMap.has(buildOption.copyFrom);
  }

  /**
   * 链表成环检测同时用一个可选参数 pathArray 记录copyFrom路径
   */
  public static isCopyFromHasCircle(checkBuildOption: BuildOptionWithName, buildOptionMap: Map<string, BuildOptionWithName>, pathArray?: string[]): boolean {
    const nameSet = new Set();
    let buildOption: BuildOptionWithName | undefined = checkBuildOption;
    while (buildOption && buildOption.copyFrom) {
      pathArray?.push(buildOption.name!);
      if (nameSet.has(buildOption.name)) {
        return true;
      } else {
        nameSet.add(buildOption.name);
      }
      buildOption = buildOptionMap.get(buildOption.copyFrom);
    }
    return false;
  }

  public static buildOptionSetToMap(buildOptionSet: BuildOptionWithName[]): Map<string, BuildOptionWithName> {
    const buildOptionMap = new Map<string, BuildOptionWithName>();
    buildOptionSet.forEach((buildOption) => {
      if (buildOption.name) {
        buildOptionMap.set(buildOption.name, buildOption);
      }
    });
    return buildOptionMap;
  }

  private static validateCopyFromNameExists(moduleModel: ModuleModel, buildOptionSet: BuildOptionWithName[], buildOptionMap: Map<string, BuildOptionWithName>) {
    buildOptionSet.forEach((buildOption) => {
      if (buildOption.copyFrom) {
        const copyFromExists = this.isCopyFromNameExists(buildOption, buildOptionMap);
        if (!copyFromExists) {
          log.warn(
            `buildOptionSet config buildOption:${buildOption.name} copyFrom name:${buildOption.copyFrom}` + ` not exists in ${moduleModel.getProfilePath()} .`,
          );
        }
      }
    });
  }

  private static validateCopyFromCircle(moduleModel: ModuleModel, buildOptionSet: BuildOptionWithName[], buildOptionMap: Map<string, BuildOptionWithName>) {
    buildOptionSet.forEach((buildOption) => {
      if (buildOption.copyFrom) {
        const copyFromPathArray: string[] = [];
        if (this.isCopyFromHasCircle(buildOption, buildOptionMap, copyFromPathArray)) {
          log.printErrorExit('IS_COPY_FROM_HAS_CIRCLE',
              [buildOption.name, copyFromPathArray.join(' -> '), moduleModel.getProfilePath()]);
        }
      }
    });
  }

  private static validateBinderSetBuildModes(buildModeNames: string[], binderSetBuildModeNames: string[], moduleModel: ModuleModel) {
    binderSetBuildModeNames.forEach((name) => {
      if (!buildModeNames.includes(name)) {
        log.printErrorExit(
          'BUILD_MODE_BINDER_ERROR',
          [name, moduleModel.getParentProject().getProfilePath()],
          [[name, moduleModel.getParentProject().getProfilePath()]]
        )
      }
    });
  }

  private static validateBinderSetTargetNames(targetNames: string[], binderTargetNames: string[], moduleModel: ModuleModel) {
    binderTargetNames.forEach((name) => {
      if (!targetNames.includes(name)) {
        log.printErrorExit(
          'TARGET_IN_BUILD_MODE_BINDER_NOT_DEFINED',
          [name, moduleModel.getProfilePath()],
          [[moduleModel.getProfilePath()]]
        )
      }
    });
  }

  private static validateBinderSetBuildOptNames(buildOptNames: (string | undefined)[], binderBuildOptNames: string[], moduleModel: ModuleModel) {
    binderBuildOptNames.forEach((name) => {
      if (!buildOptNames.includes(name)) {
        log.printErrorExit(
          'BUILD_OPTION_IN_BUILD_MODE_BINDER_NOT_DEFINED',
          [name, moduleModel.getProfilePath()],
          [[moduleModel.getProfilePath()]]
        )
      }
    });
  }
}

export class BuildOptionCombiner {
  /**
   * 后面的同名BuildOption整体替换前面的BuildOption
   * opt2 replace opt1
   */
  public static replaceBuildOption(opt1: BuildOptionWithName, opt2: BuildOptionWithName): BuildOptionWithName {
    if (opt1.name !== opt2.name) {
      throw new Error('only buildOption with same name can be replaced');
    }
    return replace(opt1, opt2);
  }

  /**
   * 合并buildOption, opt2覆盖合并opt1
   * 对于数组，假如 key 在参数 mergeArrayKeys 中则将数组元素合并到同一数组中且 opt2 的元素排在前，否则会直接替换覆盖
   */
  public static mergeBuildOption(
    opt1: BuildOptionWithName | undefined,
    opt2: BuildOptionWithName,
    mergeArrayKeys: ReadonlyArray<string> = [],
  ): BuildOptionWithName {
    return opt1 ? merge(opt1, opt2, mergeArrayKeys) : opt2;
  }

  /**
   * 读取改变后的BuildOption 来覆写原有的BuildOptionMap
   */
  public static mergeBuildOptionPath(opt: BuildOptionWithName, path: string, map: Map<string, Attributes>): Map<string, Attributes> {
    const newMap = readOpt(opt, path);
    return mergeBuildOptPath(map, newMap);
  }

  public static mergeProductBuildOptionPath(map1: Map<string, Attributes>, map2: Map<string, Attributes>): Map<string, Attributes> {
    for (const [key, value] of map1) {
      if (!map2.has(key)) {
        map2.set(key, value);
      }
    }
    return map2;
  }

  /**
   * 处理 buildOption 中copyFrom链的合并逻辑
   */
  public static processCopyFrom(buildOptionSet: BuildOptionWithName[]) {
    const buildOptionMap = BuildOptionUtil.buildOptionSetToMap(buildOptionSet);
    for (let i = 0; i < buildOptionSet.length; i++) {
      const buildOption = buildOptionSet[i];
      if (buildOption.copyFrom) {
        if (!BuildOptionUtil.isCopyFromHasCircle(buildOption, buildOptionMap)) {
          const mergedBuildOption = doCopyFromMerge(buildOption, buildOptionMap);
          if (mergedBuildOption) {
            buildOptionSet[i] = mergedBuildOption;
          }
        }
      }
    }
  }
}

function replace<T extends Option>(opt1: T, opt2: T): T {
  return cloneDeep(opt2);
}

/**
 * 非同名字段合并，同名非object类型字段后面的替换复写前面的，同名object类型字段合并复写
 * 对于数组，假如 key 在参数 mergeArrayKeys 中则将数组元素合并到同一数组中且 opt2 的元素排在前，否则会直接替换覆盖
 *
 * opt2 merge with opt1
 */
function merge<T extends Option>(opt1: T, opt2: T, mergeArrayKeys: ReadonlyArray<string> = [], parentFullKey = ''): T {
  const mergedKeys: Set<keyof T> = new Set([...Object.keys(opt1), ...Object.keys(opt2)]);
  const combinedOption: T = {} as T;

  mergedKeys.forEach((key) => {
    const value1 = cloneDeep(opt1[key]);
    const value2 = cloneDeep(opt2[key]);

    const fullKey = typeof key === 'string' ? (parentFullKey ? `${parentFullKey}.${key}` : key) : '';
    const shouldMergeAsArray = typeof key === 'string' && Array.isArray(value1) && Array.isArray(value2) && mergeArrayKeys.includes(fullKey);

    if (key in opt1 && key in opt2) {
      if (shouldMergeAsArray) {
        combinedOption[key as keyof T] = [...value2, ...value1] as T[keyof T];
      } else if (isObject(value1) && isObject(value2)) {
        (combinedOption[key] as Option) = merge(value1 as Option, value2 as Option, mergeArrayKeys, fullKey);
      } else {
        combinedOption[key] = value2;
      }
    } else if (key in opt1) {
      combinedOption[key] = value1;
    } else if (key in opt2) {
      combinedOption[key] = value2;
    }
  });
  return combinedOption;
}

/**
 * 读取传入的BuildOption path， 返回对应的BuildOptionPathMap
 * @param opt BuildOption
 * @param srcPath path
 */
export function readOpt(opt: Option | undefined, srcPath: string): Map<string, Attributes> {
  let optMap: Map<string, Attributes> = new Map();
  if (!opt) {
    return optMap;
  }
  Object.keys(opt).forEach( key => {
    const value = opt[key];
    if (isObject(value)) {
      optMap = new Map<string, Attributes>([...optMap, ...readOpt(value as Option, srcPath)]);
    } else {
      optMap.set(String(key), { value: String(value), srcPath: srcPath });
    }
  });
  return optMap;
}

/**
 * 合并两个BuildOptionPathMap
 * 值相等的时候优先级 map1 > map2
 * 值不相等的时候优先级 map1 < map2
 * @param map1
 * @param map2
 */
export function mergeBuildOptPath(
  map1: Map<string, Attributes>,
  map2: Map<string, Attributes>): Map<string, Attributes> {
  const keys = new Set([...Array.from(map1.keys()), ...Array.from(map2.keys())]);
  const resultMap: Map<string, Attributes> = new Map();
  keys.forEach((key) => {
    const value1 = map1.get(key);
    const value2 = map2.get(key);
    if (value1 && value2) {
      if (value1.value === value2.value) {
        resultMap.set(key, value1);
      } else {
        resultMap.set(key, value2);
      }
    } else if (value1) {
      resultMap.set(key, value1);
    } else if (value2) {
      resultMap.set(key, value2);
    }
  });
  return resultMap;
}

function isObject(value: unknown): boolean {
  return value !== null && typeof value === 'object' && !Array.isArray(value);
}

/**
 * do copyFrom merge
 *
 * @param buildOption 要合并的 buildOption
 * @param buildOptionMap
 */
function doCopyFromMerge(buildOption: BuildOptionWithName, buildOptionMap: Map<string, BuildOptionWithName>): BuildOptionWithName | undefined {
  const copyFromPathArray: string[] = [];
  let tmp: BuildOptionWithName | undefined = buildOption;
  while (tmp && tmp.name) {
    copyFromPathArray.push(tmp.name);
    if (tmp.copyFrom) {
      tmp = buildOptionMap.get(tmp.copyFrom);
    } else {
      // 无 copyFrom字段退出
      break;
    }
  }
  if (copyFromPathArray.length < 1) {
    return undefined;
  }
  if (copyFromPathArray.length === 1) {
    return buildOption;
  }

  // 复制方向 A->B->C, 合并时要反方向依次合并 (C->B)->A
  const reverseArray = copyFromPathArray.reverse(); // 先逆序
  let result = buildOptionMap.get(reverseArray[0]);
  for (let i = 1; i < reverseArray.length; i++) {
    const next = buildOptionMap.get(reverseArray[i]);
    if (result && next) {
      result = BuildOptionCombiner.mergeBuildOption(result, next);
    }
  }
  return result;
}
